/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 * Description: redirect printk buf for common and storage system
 * Author: xietangxin
 * Create: 2022.02.24
 */

#ifdef CONFIG_EULEROS_REDIRECT_PRINTK_BUF

#include "redirect_printk.h"
#include <linux/memblock.h>
#include <linux/ioport.h>
#include <linux/printk.h>
#include <linux/acpi.h>

#define PRB_AVGBITS 5

static struct resource printk_res = {
	.name  = "Printk reserved",
	.start = 0,
	.end   = 0,
	.flags = IORESOURCE_BUSY | IORESOURCE_MEM
};

/* hmem varies for redirect */
unsigned long printk_area_start_addr;
unsigned long printk_area_size;
static unsigned long printk_ringbuffer_start_addr;
static unsigned long printk_buf_size;
static struct printk_management_st *printk_management;

void mark_printk_rodata(void)
{
	struct printk_area_head_st *printk_area_head = NULL;

	if (printk_area_start_addr == 0)
		return;

	printk_area_head = (struct printk_area_head_st *) printk_area_start_addr;
	pr_info("Write protecting the printk ro data:\n");

	pr_info("ro1=0x%pK, size=0x%lx\n", (char *)printk_area_head->ro1, PAGE_SIZE);
	(void)set_memory_ro((unsigned long)printk_area_head->ro1, 1);

	pr_info("ro2=0x%pK, size=0x%lx\n", (char *)printk_area_head->ro2, PAGE_SIZE);
	(void)set_memory_ro((unsigned long)printk_area_head->ro2, 1);

	pr_info("ro3=0x%pK, size=0x%lx\n",
		(char *)printk_area_start_addr + printk_area_size - PAGE_SIZE, PAGE_SIZE);
	(void)set_memory_ro((unsigned long)printk_area_start_addr + printk_area_size - PAGE_SIZE, 1);
}

static int parse_mem(char *p, unsigned long *start_at, unsigned long *mem_size)
{
	char *org_p = p;

	if (p == NULL)
		return -EINVAL;

	*mem_size = memparse(p, &p);
	if (p == org_p) {
		printk(KERN_ERR "Invalid parameter.\n");
		return -EINVAL;
	}

	if (*p == '@') {
		*start_at = memparse(p + 1, &p);
	} else {
		printk(KERN_ERR "parameter format must be \'size@start\'.\n");
		return -EINVAL;
	}
	return *p == '\0' ? 0 : -EINVAL;
}

static void cal_count_and_size_bits(unsigned long size, unsigned int *count_bits, unsigned int *size_bits)
{
	*count_bits = ilog2(size / (sizeof(struct prb_desc) + sizeof(struct printk_info) + (1U << PRB_AVGBITS)));
	*size_bits = *count_bits + PRB_AVGBITS;
}

static void update_prb_data_addr(struct printk_management_st *printk_manage, unsigned long prb_start_addr)
{
	char *log_buf;
	struct prb_desc *descs;
	struct printk_info *infos;

	descs = (struct prb_desc *)prb_start_addr;
	infos = (struct printk_info *)(prb_start_addr + _DESCS_COUNT(printk_manage->count_bits) * sizeof(struct prb_desc));
	log_buf = (char *)(infos) + _DESCS_COUNT(printk_manage->count_bits) * sizeof(struct printk_info);

	printk_manage->printk_ringbuffer.desc_ring.descs = descs;
	printk_manage->printk_ringbuffer.desc_ring.infos = infos;
	printk_manage->printk_ringbuffer.text_data_ring.data = log_buf;
}

static void printk_ringbuffer_init(struct printk_management_st *printk_manage, unsigned long prb_start_addr)
{
	char *new_log_buf;
	struct prb_desc *new_descs;
	struct printk_info *new_infos;

	new_descs = (struct prb_desc *)prb_start_addr;
	new_infos = (struct printk_info *)(prb_start_addr + _DESCS_COUNT(printk_manage->count_bits) * sizeof(struct prb_desc));
	new_log_buf = (char *)(new_infos) + _DESCS_COUNT(printk_manage->count_bits) * sizeof(struct printk_info);

	prb_init(&printk_manage->printk_ringbuffer, new_log_buf,
		printk_manage->size_bits, new_descs, printk_manage->count_bits, new_infos);
}

static void clear_global_param(void)
{
	printk_area_start_addr = 0;
	printk_area_size = 0;
	printk_ringbuffer_start_addr = 0;
	printk_buf_size = 0;
}

static void __init redirect_printk_buf(bool is_check_area_size_change)
{
	cal_count_and_size_bits(printk_buf_size, &printk_management->count_bits,
		&printk_management->size_bits);
	printk_ringbuffer_init(printk_management, printk_ringbuffer_start_addr);
	printk_management->power_flag = POWERDOWN_FLAG;

	add_to_new_prb(&printk_management->printk_ringbuffer);
	printk_management->printk_area_size = printk_area_size;
}

static int __init log_redirect_setup(char *str)
{
	struct printk_area_head_st *printk_area_head = NULL;
	int ret = 0;
	bool is_check_area_size_change = true;

	if (parse_mem(str, &printk_area_start_addr, &printk_area_size)) {
		printk(KERN_ERR "Replace ring buffer failed.\n");
		clear_global_param();
		return 1;
	}

	if (printk_area_size < MIN_BUF_SIZE) {
		printk(KERN_ERR "Memory size < 0x%lx. Replace ring buffer failed.\n", MIN_BUF_SIZE);
		clear_global_param();
		return 1;
	}

	/* printk buf define buffer len type is u32 */
	if (printk_area_size > SIZE_128M) {
		printk(KERN_ERR "Memory size must less than 0x%lx. Replace ring buffer failed.\n", SIZE_128M);
		clear_global_param();
		return 1;
	}

	if ((printk_area_start_addr % PAGE_SIZE) || (printk_area_size % PAGE_SIZE)) {
		printk(KERN_ERR "Start address and memory size must be page aligned. "
					"Replace ring buffer failed.\n");
		clear_global_param();
		return 1;
	}

	/* we design struct printk_management_st more than PAGE_SIZE*/
	if (sizeof(struct printk_management_st) > PAGE_SIZE) {
		printk(KERN_ERR "management area exceed PAGE_SIZE, replace ring buffer fail\n");
		clear_global_param();
		return 1;
	}

/**********************
*		Memory distribution
*		-----------------------------------------------------
*		| PAGE_SIZE  RO                                     |
*		-----------------------------------------------------
*		| PAGE_SIZE  manage_data  (printk_management_st)    |
*		-----------------------------------------------------
*		| PAGE_SIZE  RO                                     |
*		-----------------------------------------------------
*		| Remaining  printk_ringbuffer:                     |
*		|            prb_descs                              |
*		|            prb_infos                              |
*		|            printk_log_buf                         |
*		-----------------------------------------------------
*		| PAGE_SIZE  RO                                     |
*		-----------------------------------------------------
***********************/

	pr_info("replace ring buffer phy start. memory len: 0x%lx.\n", printk_area_size);
	if (!acpi_disabled) {
		printk_area_start_addr = memblock_find_in_range(printk_area_start_addr,
								printk_area_start_addr + printk_area_size,
								printk_area_size,
								PAGE_SIZE);

		if (printk_area_start_addr == 0) {
			pr_info("alloc printk buffer fail.\n");
			return 1;
		}

		ret = memblock_reserve(printk_area_start_addr, printk_area_size);
		if (ret != 0) {
			pr_info("reserve printk buffer fail.\n");
			return 1;
		}
	}

	printk_res.start = printk_area_start_addr;
	printk_res.end = printk_area_start_addr + printk_area_size - 1;
	insert_resource(&iomem_resource, &printk_res);

	printk_area_start_addr = (unsigned long)__va(printk_area_start_addr);

	printk_area_head = (struct printk_area_head_st *) printk_area_start_addr;
	printk_management = (struct printk_management_st *)printk_area_head->manage_data;

	printk_ringbuffer_start_addr = printk_area_start_addr + sizeof(struct printk_area_head_st);
	printk_buf_size = printk_area_size - sizeof(struct printk_area_head_st) - PAGE_SIZE;

	redirect_printk_buf(is_check_area_size_change);
	return 1;
}
__setup("log_redirect=", log_redirect_setup);

#endif

